from typing import Generic, Sequence, Optional, TypeVar, Type, Dict, Any
from sqlmodel import select, delete
from pydantic import UUID4
from pydantic import BaseModel
from sqlmodel import Session
from app import models

ModelType = TypeVar("ModelType", bound=models.SQLModelBase)
CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)


class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
    db: Session
    model: Type[ModelType]

    def __init__(self, db: Session):
        self.db = db

    async def find(self, id: int) -> Optional[ModelType]:
        stmt = select(self.model).where(self.model.id == id)
        result = self.db.exec(stmt)
        return result.one_or_none()

    async def find_by(self, kwargs: Dict[str, Any]) -> Optional[ModelType]:
        filters = [getattr(self.model, key) == value for key, value in kwargs.items()]
        stmt = select(self.model).where(*filters)
        result =  self.db.exec(stmt)
        return  result.one_or_none()

    async def find_all(
        self, where: Dict[str, Any] = {}, order_by: Optional[Any] = None
    ) -> Sequence[ModelType]:
        filters = [getattr(self.model, key) == value for key, value in where.items()]
        stmt = select(self.model).where(*filters)
        if order_by is not None:
            stmt = stmt.order_by(order_by)
        result =  self.db.exec(stmt)
        return result.scalars().all()

    async def create(self, data: CreateSchemaType) -> ModelType:
        instance = self.model.model_validate(data)
       
        self.db.add(instance)
        try:
            self.db.commit()
        except Exception as e:
            self.db.rollback()
            raise e
        self.db.refresh(instance)
        return instance

    async def update(
        self,
        instance: ModelType,
        data: UpdateSchemaType,
    ) -> ModelType:
        data_dictionary = data.model_dump(exclude_unset=True)
        for key, value in data_dictionary.items():
            setattr(instance, key, value)
        self.db.add(instance)
        try:
            self.db.commit()
        except Exception as e:
            self.db.rollback()
            raise e
        self.db.refresh(instance)
        return instance

    async def delete(self, id: int) -> ModelType:
        instance = await self.find(id)
        if instance is None:
            raise Exception(f"{self.model.__name__} not found")
        try:
            self.db.delete(instance)
            self.db.commit()
        except Exception as e:
            self.db.rollback()
            raise e
        return instance

    async def delete_all(self, where: Dict[str, Any]) -> bool:
        filters = [getattr(self.model, key) == value for key, value in where.items()]
        stmt = delete(self.model).where(*filters)
        self.db.exec(stmt)
        try:
            self.db.commit()
        except Exception as e:
            self.db.rollback()
            raise e
        return True
